# 代码说明：
'''
代码功能： 基于EdgeDriver爬取taobao（淘宝）平台商品列表数据
输入参数:  KEYWORLD --> 搜索商品“关键词”；
          pageStart --> 爬取起始页；
          pageEnd --> 爬取终止页；
输出文件：爬取商品列表数据
        'Page'        ：页码
        'Num'         ：序号
        'title'       ：商品标题
        'Price'       ：商品价格
        'Deal'        ：商品销量
        'Location'    ：地理位置
        'Shop'        ：商品
        'IsPostFree'  ：是否包邮
        'Title_URL'   ：商品详细页链接
        'Shop_URL'    ：商铺链接
        'Img_URL'     ：图片链接
'''
# 声明第三方库/头文件
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
import time
import openpyxl as op               #导入Excel读写库
import mysql.connector



# 数据库连接
db = mysql.connector.connect(user='test', password='123456abc',
                              host='localhost',
                              database='mall')
cursor = db.cursor()




# 插入商品信息
#def insert_product(product):
  #  add_product = ("INSERT INTO pms_product "
        #           "(brand_id, product_category_id, product_attribute_category_id, name, pic, publish_status, sub_title, description, original_price,  price, stock, low_stock, album_pics,detail_html,detail_mobile_html,brand_name,product_category_name,product_sn,delete_status)"
         #          "VALUES (%(brand_id)s, %(product_category_id)s, %(product_attribute_category_id)s, %(title)s, %(img_url)s, %(publish_status)s, %(title)s, %(title)s, %(price)s, %(price)s, %(stock)s, %(low_stock)s, %(img_url)s, %(detail_html)s, %(detail_mobile_html)s, %(brand_name)s, %(product_category_name)s, %(product_sn)s, %(delete_status)s)")
  #  cursor.execute(add_product, product)
 #   db.commit()

# 全局变量
count = 1                           # 写入Excel商品计数
 


# 启动EdgeDriver服务
options = webdriver.EdgeOptions()
# 关闭自动测试状态显示 // 会导致浏览器报：请停用开发者模式
#options.add_experimental_option("excludeSwitches", ['enable-automation'])
options.add_argument('--disable-sync')
# 把Edge设为selenium驱动的浏览器代理；
driver = webdriver.Edge(options=options)
# 窗口最大化
# driver.maximize_window()
# wait是Selenium中的一个等待类，用于在特定条件满足之前等待一定的时间(这里是15秒)。
# 如果一直到等待时间都没满足则会捕获TimeoutException异常
wait = WebDriverWait(driver,10)
# 打开页面后会强制停止10秒，请在此时手动扫码登陆
 
# 输入“关键词”，搜索，并进行首次爬取
def search_goods(KEYWORD,start_page,total_pages):
    try:
        print('正在搜索: ')
        
        driver.get('https://www.taobao.com')
        # time.sleep(10)           # 强制停止10秒，请在此时手动扫码登陆
        driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument",
                               {"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"""})
        # 找到搜索“输入框”
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))
        # 找到“搜索”按钮
        submit = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_TSearchForm > div.search-button > button')))
        # 输入框写入“关键词KeyWord”
        input.send_keys(KEYWORD)
        # 点击“搜索”按键
        submit.click()
        # 搜索商品后会再强制停止5秒，如有滑块请手动操作
        time.sleep(15)
 
        # 如果不是从第一页开始爬取，就滑动到底部输入页面然后跳转
        if start_page != 1:
            # 滑动到页面底端
            # driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            # 滑动到底部后停留3s
            time.sleep(5)
            # 找到输入“页面”的表单，输入“起始页”
            pageInput = wait.until(EC.presence_of_element_located(
                (By.XPATH, '//*[@id="pageContent"]/div[1]/div[3]/div[4]/div/div/span[3]/input')))
            pageInput.send_keys(start_page)
            # 找到页面跳转的“确定”按钮，并且点击
            admit = wait.until(EC.element_to_be_clickable(
                (By.XPATH, '//*[@id="pageContent"]/div[1]/div[3]/div[4]/div/div/button[3]')))
            admit.click()
 
        # 获取商品信息
        get_goods(start_page)
 
        # 翻页操作
        for i in range(start_page + 1, total_pages+1):
            page_turning(i)
 
    except TimeoutException:
        print("search_goods: error")
        return search_goods(KEYWORD,start_page,total_pages)
 
# 翻页函数
def page_turning(page_number):
    print('正在翻页: ', page_number)
    try:
        # 强制等待2秒后翻页
        time.sleep(2)
        # 找到“下一页”的按钮
        submit = wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="pageContent"]/div[1]/div[3]/div[4]/div/div/button[2]')))
        submit.click()
        # 判断页数是否相等
        wait.until(EC.text_to_be_present_in_element((By.XPATH, '//*[@id="sortBarWrap"]/div[1]/div[2]/div[2]/div[8]/div/span/em'), str(page_number)))
        get_goods(page_number)
    except TimeoutException:
        print("page_number: error")
        page_turning(page_number)
 
# 获取每一页的商品信息；
def get_goods(page):
    # 声明全局变量count
    global count


    # 获取屏幕高度
    screen_height = driver.execute_script("return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;")
    # 获取页面总高度
    total_height = driver.execute_script("return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);")
    # 模拟滚动
    scroll_interval = 1000  # 每次滚动的像素数
    current_height = screen_height
    while current_height < total_height:
        driver.execute_script(f"window.scrollTo(0, {current_height});")
        time.sleep(1)  # 等待图片加载
        current_height += scroll_interval
    # 到达底部后，再等待一段时间，确保所有图片都加载完成
    # time.sleep(5)

    # 滑动到页面底端
    # driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    # 获取商品前固定等待10秒,刷新/滑动界面，使所有信息都加载完成
    time.sleep(5)
 
    html = driver.page_source
    doc = pq(html)
    # 提取所有商品的共同父元素的类选择器
    items = doc('div.PageContent--contentWrap--mep7AEm > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Content--content--sgSCZ12 > div > div').items()
 
    for item in items:
        # 定位商品标题
        name = item.find('.Title--title--jCOPvpf span').text()
        # 定位价格
        price_int = item.find('.Price--priceInt--ZlsSi_M').text()
        price_float = item.find('.Price--priceFloat--h2RR0RK').text()
        if price_int and price_float:
            price = float(f"{price_int}{price_float}")
        else:
            price = 0.0
        # 定位交易量
        sale = item.find('.Price--realSales--FhTZc7U').text()
        # 定位所在地信息
        location = item.find('.Price--procity--_7Vt3mX').text()
        # 定位店名
        shop = item.find('.ShopInfo--TextAndPic--yH0AZfx a').text()
        # 定位包邮的位置
        postText = item.find('.SalesPoint--subIconWrapper--s6vanNY span').text()
        postText = "包邮" if "包邮" in postText else "暂无"
        # 定位商品url
        t_url = item.find('.Card--doubleCardWrapper--L2XFE73')
        t_url = t_url.attr('href')
        # print(t_url)
        # 定位店名url
        shop_url = item.find('.ShopInfo--TextAndPic--yH0AZfx a')
        shop_url = shop_url.attr('href')
        # print(shop_url)
        # 定位商品图片url
        img = item.find('.MainPic--mainPicWrapper--iv9Yv90 img')
        pic = img.attr('src')
        # print(img_url)
 
        # 构建商品信息字典
        # product = {
        #     'Page': page,
        #     'Num': count-1,
        #     'title': title,
        #     'price': price,
        #     'deal': deal,
        #     'location': location,
        #     'shop': shop,
        #     'isPostFree': postText,
        #     'url': t_url,
        #     'shop_url': shop_url,
        #     'img_url': img_url,

        #     'brand_id': 2, # 品牌id 
        #     'product_category_id': 34,     # 商品分类id
        #     'product_attribute_category_id': 3,     # 属性类型id
        #     'publish_status': 1,     # 发布状态
        #     'stock': 10000,     # 库存
        #     'low_stock': 99,     # 低库存
        #     'detail_html': "<p>电脑详情</p>",     # 电脑端详情
        #     'detail_mobile_html': "<p>移动详情</p>",     # 移动端详情
        #     'brand_name': "三星",     # 品牌名称
        #     'product_category_name': "智能设备" ,    # 商品分类名称
        #     'product_sn': "产品编码" ,    # 产品编码 
        #     'delete_status': 0     # 删除状态 
        # }
        product = {
            'brand_id': 60,                            # 品牌ID
            'product_category_id': 57,                 # 产品所属的类别ID
            'feight_template_id': 0,                   # 运费模板ID
            'product_attribute_category_id': 16,        # 产品属性分类ID
            'name': name,   # 产品名称
            'pic': pic,  # 产品主图链接
            'product_sn': '产品序列号',                   # 产品序列号
            'delete_status': 0,                        # 删除状态，0通常表示未删除
            'publish_status': 1,                       # 发布状态，1通常表示已发布
            'new_status': 1,                           # 新品状态，1通常表示新品
            'recommand_status': 1,                     # 推荐状态，1通常表示推荐
            'verify_status': 0,                        # 审核状态，0可能表示未审核
            'sort': 0,                                 # 排序权重
            'sale': 999,                                # 销售数量
            'price':price,                          # 当前价格
            'promotion_price': price,                # 促销价格
            'gift_growth': price,                       # 购买此产品获得的成长值
            'gift_point': price,                        # 购买此产品获得的积分
            'use_point_limit': 0,                      # 使用积分的限制
            'sub_title': '【限时限量抢购】'+name+' >> 勾选[保障服务]，获得全方位服务计划，延保售后无忧。',  # 产品副标题
            'description': '【限时限量抢购】'+name+' >> 勾选[保障服务]，获得全方位服务计划，延保售后无忧。',                         # 产品描述
            'original_price': price,                 # 原价
            'stock': 10000,                              # 库存量
            'low_stock': 100,                            # 低库存阈值
            'unit': '',                                # 单位
            'weight': 0.00,                            # 产品重量
            'preview_status': 0,                       # 预览状态
            'service_ids': '1,2,3',                    # 服务ID列表，用逗号分隔
            'keywords': '',                            # 搜索关键词
            'note': '',                                # 内部备注
            'album_pics': pic,                          # 图册图片链接列表
            'detail_title': '【限时限量抢购】'+name+' >> 勾选[保障服务]，获得全方位服务计划，延保售后无忧。',         # 详情页标题
            'detail_desc': '【限时限量抢购】'+name+' >> 勾选[保障服务]，获得全方位服务计划，延保售后无忧。',       # 详情页描述
            'detail_html': '<div><img src='+pic+' /> ...',  # 详情页HTML
            'detail_mobile_html': '<div><img src='+pic+' /> ...' ,                  # 移动端详情页HTML
            'promotion_start_time': '2024-10-01 15:12:54',  # 促销开始时间
            'promotion_end_time': '2024-10-07 00:00:00',    # 促销结束时间
            'promotion_per_limit': 0,                  # 促销每人限购数量
            'promotion_type': 1,                       # 促销类型，数字或枚举值
            'brand_name': '精选美食',                       # 品牌名称
            'product_category_name': '美食'         # 产品类别名称
        }


        print(product)

       
 
        # 商品信息写入Excel表格中
        wb.cell(row=count, column=1, value=page)                # 页码
        wb.cell(row=count, column=2, value=count-1)             # 序号
        wb.cell(row=count, column=3, value=name)               # 标题
        wb.cell(row=count, column=4, value=price)               # 价格
        wb.cell(row=count, column=5, value=sale)                # 付款人数
        wb.cell(row=count, column=6, value=location)            # 地理位置
        wb.cell(row=count, column=7, value=shop)                # 店铺名称
        wb.cell(row=count, column=8, value=postText)            # 是否包邮
        wb.cell(row=count, column=9, value=t_url)               # 商品链接
        wb.cell(row=count, column=10, value=shop_url)           # 商铺链接
        wb.cell(row=count, column=11, value=pic)            # 图片链接
        count += 1     
        
        insert_product(product)                                         # 下一行

# 插入商品信息
def insert_product(product):
    add_product = (
        "INSERT INTO `mall`.`pms_product` "
        "(brand_id, product_category_id, feight_template_id, product_attribute_category_id, "
        "name, pic, product_sn, delete_status, publish_status, new_status, recommand_status, "
        "verify_status, sort, sale, price, promotion_price, gift_growth, gift_point, "
        "use_point_limit, sub_title, description, original_price, stock, low_stock, "
        "unit, weight, preview_status, service_ids, keywords, note, album_pics, "
        "detail_title, detail_desc, detail_html, detail_mobile_html, "
        "promotion_start_time, promotion_end_time, promotion_per_limit, promotion_type, "
        "brand_name, product_category_name) "
        "VALUES "
        "(%(brand_id)s, %(product_category_id)s, %(feight_template_id)s, %(product_attribute_category_id)s, "
        "%(name)s, %(pic)s, %(product_sn)s, %(delete_status)s, %(publish_status)s, %(new_status)s, "
        "%(recommand_status)s, %(verify_status)s, %(sort)s, %(sale)s, %(price)s, "
        "%(promotion_price)s, %(gift_growth)s, %(gift_point)s, %(use_point_limit)s, "
        "%(sub_title)s, %(description)s, %(original_price)s, %(stock)s, %(low_stock)s, "
        "%(unit)s, %(weight)s, %(preview_status)s, %(service_ids)s, %(keywords)s, "
        "%(note)s, %(album_pics)s, %(detail_title)s, %(detail_desc)s, %(detail_html)s, "
        "%(detail_mobile_html)s, %(promotion_start_time)s, %(promotion_end_time)s, "
        "%(promotion_per_limit)s, %(promotion_type)s, %(brand_name)s, "
        "%(product_category_name)s)"
    )
    cursor.execute(add_product, product)
    db.commit()

    # SQL查询语句
    query = "SELECT id FROM pms_product ORDER BY id DESC LIMIT 1"
    cursor.execute(query) 
    result = cursor.fetchone()     # 获取查询结果
    latest_id = result[0] if result else None # 提取最新ID
    print("Latest ID:", latest_id)   # 打印最新ID

    # 创建包含SKU库存信息的字典
    sku = {
        'product_id': latest_id,                          # 对应的产品ID
        'sku_code': 'SKU编码',             # SKU编码
        'price': product['price']  ,                          # 价格
        'stock': 10000,                               # 库存量
        'low_stock': 100,                         # 低库存警告阈值，这里使用None表示NULL
        'pic': product['pic']  ,                                # SKU图片链接，这里使用None表示NULL
        'sale':product['sale']  ,                            # 销售数量，这里使用None表示NULL
        'promotion_price': product['promotion_price']  ,                  # 促销价格
        'lock_stock': -8,                          # 锁定库存量
        'sp_data': '[{"key":"暂无","value":"暂无"}]'  # SKU属性数据，JSON格式
    }
    # 准备SQL语句以插入数据
    add_sku= (
        "INSERT INTO `mall`.`pms_sku_stock` "
        "(product_id, sku_code, price, stock, low_stock, pic, sale, promotion_price, lock_stock, sp_data) "
        "VALUES "
        "(%(product_id)s, %(sku_code)s, %(price)s, %(stock)s, %(low_stock)s, "
        "%(pic)s, %(sale)s, %(promotion_price)s, %(lock_stock)s, %(sp_data)s)"
    )

    # 执行SQL语句并提交事务
    cursor.execute(add_sku, sku)
    db.commit()



# 爬虫main函数
def Crawer_main(KEYWORD,pageStart,pageEnd):
    try:
        # 爬取从pageStart到pageAll页的数据
        search_goods(KEYWORD,pageStart,pageEnd)
    except Exception as exc:
        print('Crawer_main函数报错:', exc)
 
 
 
 
if __name__ == '__main__':
 
    # 创建游标对象
    cursor = db.cursor()
    # 定义 SQL 删除语句
    table_name = 'mall'
    delete_query = f"DELETE FROM a_products;"
    # 执行 SQL 删除语句
    cursor.execute(delete_query)
    # 提交事务
    db.commit()

    KEYWORD = input('输入搜索的商品关键词Keyword：')           # 要搜索的商品的关键词
    pageStart = int(input('输入爬取的起始页PageStart：'))     # 爬取起始页
    pageEnd = int(input('输入爬取的终止页PageEnd：'))         # 爬取终止页
 
    # 建立Excel表格
    try:
        ws = op.Workbook()                                  # 创建Workbook
        wb = ws.create_sheet(index=0)                       # 创建worsheet
        # Excel第一行：表头
        title_list = ['Page', 'Num', 'title', 'Price', 'Deal', 'Location', 'Shop', 'IsPostFree', 'Title_URL',
                      'Shop_URL', 'Img_URL']
        for i in range(0, len(title_list)):
            wb.cell(row=count, column=i + 1, value=title_list[i])
        count += 1  # 从第二行开始写爬取数据
    except Exception as exc:
        print("Excel建立失败！")
 
    # 开始爬取数据
    Crawer_main(KEYWORD,pageStart,pageEnd)
 
    # 保存Excel表格
    Filename = input("输入存储文件名称：")
    Filename = Filename + '(_From_Taobao).xlsx'
    ws.save(filename = Filename)
    print(Filename + "存储成功~")